﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Wintellect.PowerCollections;
using DbSharp.Framework.Storage.IO;
using System.Diagnostics;
using DbSharp.Framework.DbObject;
using DbSharp.Framework.Driver;

namespace DbSharp.Framework.Assertion.Xml
{
    partial class AssertionHelper
    {
        #region Private methods

        private static bool IsValidArgsForSetCount(object enumerableArg, object arg2)
        {
            if (IsEnumerable(enumerableArg))
            {
                if (IsEnumerable(arg2))//compare with another list
                    return true;
                else if (IsIntNumber(arg2))//compare with integer value
                    return true;
                else
                    return false;
            }
            else
                return false;
        }


        #endregion


        public static void VerifyRecordsetArguments(object actual, object expected)
        {
            if ((!(actual is IEnumerable<XElement>))
                || (!(expected is IEnumerable<XElement>)))
                throw new ArgumentException("Actual resultset and expected resultset must be IEnumerable<XElement>.");
        }

        [Obsolete]
        public static void VerifyRecordsetsHaveSameFields(List<string> actualFieldList, List<string> expectedFieldList)
        {
            StringBuilder sb1 = new StringBuilder();
            StringBuilder sb2 = new StringBuilder();

            foreach (var fieldName in actualFieldList)
            {
                sb1.AppendLine(fieldName);
            }

            foreach (var fieldName in expectedFieldList)
            {
                sb2.AppendLine(fieldName);
            }

            if (false == string.Equals(sb1.ToString(), sb2.ToString(), StringComparison.Ordinal))
                throw new ArgumentException("Fields' name and count in actual resultset and expected resultset must be identical.");
        }


        public static void VerifyRecordsetsHaveSameFields(List<FieldMetaData> actualMetaData, List<FieldMetaData> expectedMetaData)
        {
            StringBuilder sb1 = new StringBuilder();
            StringBuilder sb2 = new StringBuilder();

            foreach (var field in actualMetaData)
            {
                sb1.AppendLine(field.ActualOutputUpperName);
            }

            foreach (var field in expectedMetaData)
            {
                sb2.AppendLine(field.ActualOutputUpperName);
            }

            if (false == string.Equals(sb1.ToString(), sb2.ToString(), StringComparison.Ordinal))
                throw new ArgumentException("Fields' name and count in actual resultset and expected resultset must be identical.");
        }


        public static void VerifyRecordsetCountClaimArgs(object actual, object expected)
        {
            if (IsValidArgsForSetCount(actual, expected) == false)
            {
                if (IsValidArgsForSetCount(expected, actual) == false)
                {
                    string message = @"Two resultsets must be IEnumerable<XElement> type, 
                            or one resultset is IEnumerable<XElement> and another must be integer value.";
                    throw new ArgumentException(message);
                }
            }
        }



        public static bool IsEnumerable(object obj)
        {
            if (obj is IEnumerable<XElement>)
                return true;
            else
                return false;
        }


        public static bool IsIntNumber(object obj)
        {
            if ((obj is Int32) || (obj is Int16) || (obj is Int64))
                return true;
            else
            {
                long tempInt;
                return Int64.TryParse(obj.ToString(), out tempInt);
            }
        }


        public static void CopyItemsIntoBag(object listSource, Bag<XElement> bagDestination)
        {
            foreach (var item in (IEnumerable<XElement>)listSource)
            {
                bagDestination.Add(item);
            }
        }
    }


    /// <summary>
    /// Ignore to compare targetResultset and referenceResultset 
    /// Operand: targetResultset and referenceResulst can be any type
    /// Result: true if the targetResultset, referenceResultset, setup and teardown can execute without error.
    /// </summary>
    class NotCareResultsetAssertion : IAssertion
    {
        #region IAssertion Members

        /// <summary>
        /// the function will return True for ever
        /// </summary>
        /// <param name="actual"></param>
        /// <param name="expected"></param>
        /// <returns></returns>
        public bool Claim(object actual, object expected, List<FieldMetaData> actualMetaData, List<FieldMetaData> expectedMetaData, ITestDataSource actualPlugin, ITestDataSource expectedPlugin)
        {
            return true;
        }

        #endregion
    }


    /// <summary>
    /// Determines whether the targetResultset and the referenceResultset contain the same elements. 
    /// The SetEquals method ignores duplicate entries and the order of elements in the other parameter. 
    /// Operand: Both are set style 
    /// Result: true if the targetResultset is equal to the referenceResultset; otherwise, false. 
    /// </summary>
    class SetEqualAssertion : IAssertion
    {
        #region IAssertion Members

        public bool Claim(object actual, object expected, List<FieldMetaData> actualMetaData, List<FieldMetaData> expectedMetaData, ITestDataSource actualPlugin, ITestDataSource expectedPlugin)
        {
            AssertionHelper.VerifyRecordsetArguments(actual, expected);
            AssertionHelper.VerifyRecordsetsHaveSameFields(actualMetaData, expectedMetaData);

            var comparer = new XElmtRecordEqualityComparer();
            Trace.TraceInformation("assert:Begin Save into Bag" + DateTime.Now.Ticks);
            Bag<XElement> actualSet = new Bag<XElement>(comparer);
            Bag<XElement> expectedSet = new Bag<XElement>(comparer);
            AssertionHelper.CopyItemsIntoBag(actual, actualSet);
            AssertionHelper.CopyItemsIntoBag(expected, expectedSet);
            Trace.TraceInformation("assert:End Save into Bag," + DateTime.Now.Ticks);
            Trace.TraceInformation("assert:Begin assert," + DateTime.Now.Ticks);
            bool result = actualSet.IsEqualTo(expectedSet);
            Trace.TraceInformation("assert:End assert," + DateTime.Now.Ticks);
            return result;
        }

        #endregion
    }


    /// <summary>
    /// Determines whether the targetResultset overlaps the referenceResultset. 
    /// Operand: Both are set style 
    /// Result: true if the targetResultset and the referenceResultset share at least one common element; otherwise, false. 
    /// </summary>
    class OverlapsAssertion : IAssertion
    {
        #region IAssertion Members

        public bool Claim(object actual, object expected, List<FieldMetaData> actualMetaData, List<FieldMetaData> expectedMetaData, ITestDataSource actualPlugin, ITestDataSource expectedPlugin)
        {
            AssertionHelper.VerifyRecordsetArguments(actual, expected);
            AssertionHelper.VerifyRecordsetsHaveSameFields(actualMetaData, expectedMetaData);

            var comparer = new XElmtRecordEqualityComparer();
            Bag<XElement> actualSet = new Bag<XElement>(comparer);
            Bag<XElement> expectedSet = new Bag<XElement>(comparer);
            AssertionHelper.CopyItemsIntoBag(actual, actualSet);
            AssertionHelper.CopyItemsIntoBag(expected, expectedSet);

            if (actualSet.IsDisjointFrom(expectedSet))
                return false;
            else
                return true;
        }

        #endregion
    }

    /// <summary>
    /// Determines whether the targetResultset is a superset of the referenceResultset.
    /// The targetResultset is a superset of the referenceResultset if every element in the referenceResultset is also in the targetResultset, at least the same number of times. 
    /// Operand: Both are set style
    /// Result: True if targetResultset is a superset of referenceResultset.
    /// 
    ///when any of the following condition is met, the assertion will be true,
    ///(1)A.Count>=B.Count>0
    ///(2)B.Count=0
    /// </summary>
    class IsSupersetOfAssertion : IAssertion
    {
        #region IAssertion Members

        public bool Claim(object actual, object expected, List<FieldMetaData> actualMetaData, List<FieldMetaData> expectedMetaData, ITestDataSource actualPlugin, ITestDataSource expectedPlugin)
        {
            AssertionHelper.VerifyRecordsetArguments(actual, expected);
            AssertionHelper.VerifyRecordsetsHaveSameFields(actualMetaData, expectedMetaData);

            var comparer = new XElmtRecordEqualityComparer();
            Bag<XElement> actualSet = new Bag<XElement>(comparer);
            Bag<XElement> expectedSet = new Bag<XElement>(comparer);
            AssertionHelper.CopyItemsIntoBag(actual, actualSet);
            AssertionHelper.CopyItemsIntoBag(expected, expectedSet);

            return actualSet.IsSupersetOf(expectedSet);
        }

        #endregion
    }

    /// <summary>
    /// Determines if targetResultset is a subset of the referenceResultset.
    /// The targetResultset is a subset of referenceResultset if every element in targetResultset is also in referenceResultset, at least the same number of times. 
    /// Operand: Both are set style
    /// Result: True if targetResultset is a subset of referenceResultset.
    /// 
    ///when any of the following condition is met, the assertion will be true,
    ///(1)B.Count>=A.Count
    ///(2)A.Count=0
    /// </summary>
    class IsSubsetOfAssertion : IAssertion
    {
        #region IAssertion Members

        public bool Claim(object actual, object expected, List<FieldMetaData> actualMetaData, List<FieldMetaData> expectedMetaData, ITestDataSource actualPlugin, ITestDataSource expectedPlugin)
        {
            AssertionHelper.VerifyRecordsetArguments(actual, expected);
            AssertionHelper.VerifyRecordsetsHaveSameFields(actualMetaData, expectedMetaData);

            var comparer = new XElmtRecordEqualityComparer();
            Bag<XElement> actualSet = new Bag<XElement>(comparer);
            Bag<XElement> expectedSet = new Bag<XElement>(comparer);
            AssertionHelper.CopyItemsIntoBag(actual, actualSet);
            AssertionHelper.CopyItemsIntoBag(expected, expectedSet);

            return actualSet.IsSubsetOf(expectedSet);
        }

        #endregion
    }


    /// <summary>
    /// Determines if targetResultset is a proper superset of the referenceResultset. 
    /// The targetResultset is a proper superset of the refereceResultset if every element in refereceResultset is also in targetResultset, at least the same number of times. 
    /// Additional, the targetResultset must have strictly more items than the referenceResultset.      
    /// Operand: Both are set style
    /// Result: True if targetResultset is a proper superset of referenceResultset.
    /// 
    ///when any of the following condition is met, the assertion will be true,
    ///(1) A.Count>B.Count
    ///(2) A.Count>0 && B.Count=0
    /// </summary>
    class IsProperSupersetOfAssertion : IAssertion
    {
        #region IAssertion Members

        public bool Claim(object actual, object expected, List<FieldMetaData> actualMetaData, List<FieldMetaData> expectedMetaData, ITestDataSource actualPlugin, ITestDataSource expectedPlugin)
        {
            AssertionHelper.VerifyRecordsetArguments(actual, expected);
            AssertionHelper.VerifyRecordsetsHaveSameFields(actualMetaData, expectedMetaData);

            var comparer = new XElmtRecordEqualityComparer();
            Bag<XElement> actualSet = new Bag<XElement>(comparer);
            Bag<XElement> expectedSet = new Bag<XElement>(comparer);
            AssertionHelper.CopyItemsIntoBag(actual, actualSet);
            AssertionHelper.CopyItemsIntoBag(expected, expectedSet);

            return actualSet.IsProperSupersetOf(expectedSet);
        }

        #endregion
    }


    /// <summary>
    /// Determines if the targetResultset is a proper subset of the referenceResultset. 
    /// The targetResulst is a subset of the referenceResultset if every element in the targetResultset is also in the referenceResultset, at least the same number of times. 
    /// Additional, the targetResultset must have strictly fewer items than the targetResultset. 
    /// Operand: Both are set style
    /// Result: True if targetResultset is a proper subset of referenceResultset.
    /// 
    /// when any of the following condition is met, the assertion will be true,
    /// (1) B.Count>A.Count
    /// (2) A.Count=0 && B.Count>0
    /// </summary>
    class IsProperSubsetOfAssertion : IAssertion
    {
        #region IAssertion Members

        public bool Claim(object actual, object expected, List<FieldMetaData> actualMetaData, List<FieldMetaData> expectedMetaData, ITestDataSource actualPlugin, ITestDataSource expectedPlugin)
        {
            AssertionHelper.VerifyRecordsetArguments(actual, expected);
            AssertionHelper.VerifyRecordsetsHaveSameFields(actualMetaData, expectedMetaData);

            var comparer = new XElmtRecordEqualityComparer();
            Bag<XElement> actualSet = new Bag<XElement>(comparer);
            Bag<XElement> expectedSet = new Bag<XElement>(comparer);
            AssertionHelper.CopyItemsIntoBag(actual, actualSet);
            AssertionHelper.CopyItemsIntoBag(expected, expectedSet);

            return actualSet.IsProperSubsetOf(expectedSet);
        }

        #endregion
    }

    /// <summary>
    /// Determines whether the targatResultset is strictly equal to the referenceResultset, include order and times of every element occurrance
    /// Operand: Both are set style
    /// Result : true if the targetResultset is equal to the referenceResultset; otherwise, false. 
    /// </summary>
    class StrictEqualAssertion : IAssertion
    {
        #region IAssertion Members

        public bool Claim(object actual, object expected, List<FieldMetaData> actualMetaData, List<FieldMetaData> expectedMetaData, ITestDataSource actualPlugin, ITestDataSource expectedPlugin)
        {
            if (actual is IEnumerable<XElement>)
            {
                #region SequenceEqual for Set
                AssertionHelper.VerifyRecordsetArguments(actual, expected);
                AssertionHelper.VerifyRecordsetsHaveSameFields(actualMetaData, expectedMetaData);

                IEnumerable<XElement> x = (IEnumerable<XElement>)actual;
                IEnumerable<XElement> y = (IEnumerable<XElement>)expected;

                if ((x == null) && (y == null))
                    return true;
                else if (x == null)
                    return false;
                else if (y == null)
                    return false;
                else if (x.Count() != y.Count())
                    return false;
                else
                {
                    var comparer = new XElmtRecordEqualityComparer();
                    using (var xEnumerator = x.GetEnumerator())
                    using (var yEnumerator = y.GetEnumerator())
                    {
                        while (xEnumerator.MoveNext())
                        {
                            yEnumerator.MoveNext();
                            if (!comparer.Equals(xEnumerator.Current, yEnumerator.Current))
                                return false;
                        }
                    }
                    return true;
                }
                #endregion
            }
            else
            {
                #region Scalar value comparison

                string xText, yText;
                if (actual == null)
                    xText = string.Empty;
                else
                    xText = actual.ToString();

                if (expected == null)
                    yText = string.Empty;
                else
                    yText = expected.ToString();

                return string.Equals(xText, yText, StringComparison.Ordinal);
                #endregion
            }

        }

        #endregion
    }



}